package cluster

import (
	"context"
	"github.com/rs/zerolog/log"
	apiv1a1 "gomod.alauda.cn/mysql-mgr-operator/api/v1alpha1"
	"k8s.io/apimachinery/pkg/fields"
	toolscache "k8s.io/client-go/tools/cache"
	"satellite/pkg/mgrclient"
	"sigs.k8s.io/controller-runtime/pkg/cache"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sort"
	"time"
)

type CreateOption struct {
	File       string
	Version    string
	Upgrade    []string
	Preserving bool
}

type Create struct {
	*CreateOption
	*Base
}

func NewCreate(opt *CreateOption) *Create {
	return &Create{
		CreateOption: opt,
	}
}

func (c *Create) WithBase(base *Base) *Create {
	c.Base = base
	return c
}

func (c *Create) Run() error {
	if !c.Preserving {
		err := c.clean()
		if client.IgnoreNotFound(err) != nil {
			return err
		}
		if err == nil {
			log.Info().Msg("clean old resource complete")
			time.Sleep(time.Second * 3)
		}
	}
	err := c.createCluster()
	if err != nil {
		return err
	}
	log.Info().Msg("create cluster complete")
	if c.Upgrade == nil {
		return nil
	}
	sort.Strings(c.Upgrade)
	for _, uv := range c.Upgrade {
		err := c.upgrade(uv)
		if err != nil {
			return err
		}
	}
	return nil
}

func (c *Create) createCluster() error {
	clusterCr := defaultCluster.DeepCopy()
	clusterCr.Name = c.Name
	clusterCr.Namespace = c.Namespace
	clusterCr.Spec.Version = c.Version
	mysqlCr := BuildMysql(clusterCr)
	var err error
	switch c.Type {
	case TypeMysql:
		err = c.create(mysqlCr)
	case TypeMgr:
		err = c.create(mysqlCr)
	default:
		err = c.create(mysqlCr)
		if err != nil {
			err = c.create(clusterCr)
		}
	}
	if err != nil {
		return err
	}
	log.Info().Str("cluster", clusterCr.Name).Str("type", c.Type).Str("namespace", clusterCr.Namespace).Str("version", clusterCr.Spec.Version).Msg("create")
	return nil
}

func (c *Create) upgrade(version string) error {
	err := c.waiteReady()
	if err != nil {
		return err
	}
	log.Info().Str("version", version).Msg("start upgrade")
	var obj client.Object
	if c.Type == TypeMysql {
		clusterCr, err := c.getMysqlCr()
		if err != nil {
			return err
		}
		clusterCr.Spec.Version = version
		obj = clusterCr
	} else {
		clusterCr, err := c.getMgrCr()
		if err != nil {
			return err
		}
		clusterCr.Spec.Version = version
		obj = clusterCr
	}
	err = c.update(obj)
	if err != nil {
		return err
	}
	return nil
}

func (c *Create) clean() (err error) {
	if c.Type == TypeMysql {
		return c.cleanMysql()
	}
	mgrCr, err := c.getMgrCr()
	if err != nil {
		return err
	}
	if mgrCr.OwnerReferences == nil {
		err = c.deleteMgrCr()
	}
	return c.cleanMysql()
}

func (c *Create) cleanMysql() error {
	mysqlCr, err := c.getMysqlCr()
	if err != nil {
		return err
	}
	if mysqlCr.Finalizers == nil {
		mysqlCr.Finalizers = make([]string, 0, 1)
	}
	mysqlCr.Finalizers = append(mysqlCr.Finalizers, pvcFinalizer)
	err = c.update(mysqlCr)
	if err != nil {
		return err
	}
	err = c.deleteMysqlCr()
	return err
}

func (c *Create) waiteReady() error {
	log.Info().Msg("waite for cluster ready")
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
	defer cancel()
	stopCh := make(chan struct{})
	defer close(stopCh)
	dataCh := make(chan *apiv1a1.MySQLCluster, 1)
	controller, err := cache.New(mgrclient.Config(), cache.Options{
		Scheme:    mgrclient.Scheme(),
		Namespace: c.Namespace,
		DefaultSelector: cache.ObjectSelector{
			Field: fields.OneTermEqualSelector("metadata.name", c.Name),
		},
	})
	if err != nil {
		return err
	}
	informer, err := controller.GetInformer(ctx, new(apiv1a1.MySQLCluster))
	if err != nil {
		return err
	}
	handler := toolscache.ResourceEventHandlerFuncs{
		UpdateFunc: func(oldObj, newObj interface{}) {
			if clusterCr, ok := newObj.(*apiv1a1.MySQLCluster); ok {
				dataCh <- clusterCr
			}
		},
	}
	_, err = informer.AddEventHandler(handler)
	if err != nil {
		return err
	}
	go controller.Start(ctx)
	for {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case clusterCr := <-dataCh:
			if clusterCr.Status.State == apiv1a1.ClusterStateOfReady {
				cancel()
				return nil
			}
		}
	}
}
